
PLUG.Platform = PLUG_PLATFORM_SERVER

PLUG.ClassName = "sv_struct"
PLUG.PrintName = "Serverside Structure"
PLUG.Author = "thomasfn"

PLUG.Hard = true

local LogFile = "gmanage/logs/"
local DataFile = "gmanage/serverdata.txt"

function PLUG:Init()
	for _, filename in pairs( file.Find( "../materials/gmanage/*" ) ) do
		resource.AddFile( "materials/gmanage/" .. filename )
	end
	self:LoadServerData()
	self:ApplySwepRestrictions()
end

function PLUG:AddHooks()
	plugs.Hook( PLUG_PLATFORM_SERVER, "PlayerInitialSpawn" )
	plugs.Hook( PLUG_PLATFORM_SERVER, "PlayerDisconnected" )
	plugs.Hook( PLUG_PLATFORM_SERVER, "PlayerSpawn" )
	plugs.Hook( PLUG_PLATFORM_SERVER, "PlayerNoClip" )
	plugs.Hook( PLUG_PLATFORM_SERVER, "PlayerSpray" )
	plugs.Hook( PLUG_PLATFORM_SERVER, "CanTool" )
	plugs.Hook( PLUG_PLATFORM_SERVER, "PlayerSpawnSENT" )
end

function PLUG:PlayerInitialSpawn( ply )
	ply:LoadPlayerData()
	timer.Create( ply:SteamID() .. "_gmanage", 60, 0, ply.DoMinute, ply )
	if (!ply._DAT) then ply._DAT = {} end
end

function PLUG:PlayerDisconnected( ply )
	timer.Destroy( ply:SteamID() .. "_gmanage" )
end

function PLUG:PlayerSpawn( ply )
	if (ply._SPAWNED) then return end
	ply:BeamPrivs()
	ply:BeamUGS()
	ply._SPAWNED = true
end

function PLUG:PlayerNoClip( ply )
	return ply:HasPriv( "noclip" )
end

function PLUG:PlayerSpray( ply )
	return ply:HasPriv( "spray" )
end

function PLUG:CanTool( ply, tr, mode )
	if (!ply:CanUseEntity( mode )) then
		ply:PrintMessage( HUD_PRINTTALK, "You do not have enough privileges to use this!" )
		return false
	end
end

function PLUG:PlayerSpawnSENT( ply, classname )
	if (!ply:CanUseEntity( classname )) then
		ply:PrintMessage( HUD_PRINTTALK, "You do not have enough privileges to spawn this!" )
		return false
	end
end

function PLUG:PlayerSpawnSWEP( ply, classname )
	if (!ply:CanUseEntity( classname )) then
		ply:PrintMessage( HUD_PRINTTALK, "You do not have enough privileges to spawn this!" )
		return false
	end
	return true
end

function PLUG:PlayerExtend( meta )
	function meta:DoMinute()
		if ((!self) || (!self:IsValid())) then return end
		self._DAT.Timer = (self._DAT.Timer or 0) + 60
		self:SetNWInt( "gman_timer", self._DAT.Timer )
		if (self:CheckAutoPromote()) then
			self._DAT.Timer = 0
			self:SetNWInt( "gman_timer", 0 )
		end
		self:SavePlayerData()
	end
	function meta:CheckAutoPromote()
		local tm = self._DAT.Timer or 0
		local gp = self:GetUsergroupTable()
		if (gp.AutoPromoteGroup && gp.AutoPromoteTime) then
			if (tm > gp.AutoPromoteTime) then
				self:SetUsergroup( gp.AutoPromoteGroup )
				plugs.Call( "SendNotice", "You have been automatically promoted to '" .. gp.AutoPromoteGroup .. "'!", self )
				self:ChatPrint( "You were automatically promoted to '" .. gp.AutoPromoteGroup .. "'!" )
				self:Log( "autopromote", "Was promoted to " .. gp.AutoPromoteGroup )
				return true
			end
		end
	end
	function meta:ServerCommand( com )
		game.ConsoleCommand( com .. "\n" )
	end
	function meta:Kick( reason )
		self:Log( "kick_bans", "Was kicked for reason '" .. reason .. "'" )
		reason = reason or "Kicked by GManage"
		self:ServerCommand( "kickid " .. tostring( self:UserID() ) .. " " .. reason )
	end
	function meta:Ban( time )
		time = time or 0
		local t = ""
		if (time == 0) then t = "ever" else t = tostring( time/60 ) .. " minutes" end
		self:Log( "kick_bans", "Was banned for " .. t )
		self:ServerCommand( "banid " .. tostring( (time or 0)/60 ) .. " " .. tostring( self:UserID() ) .. " kick \"Banned by GManage\"" )
		local reason = "Banned"
		if ((time or 0) == 0) then reason = reason .. " (PERMA)" end
		self:ServerCommand( "writeid" )
		self:Kick( reason )
	end
	function meta:Log( id, message )
		plugs.Call( "LogMessage", id, "(" .. self:SteamID() .. ") " .. message )
	end
	function meta:LoadPlayerData()
		local dat = plugs.Call( "LoadPlayerData", self )
		if (!dat) then
			local defgrp = plugs.Call( "GetDefaultUsergroup" )
			if (!defgrp) then
				ErrorNoHalt( "Could not find default usergroup!" )
				self._DAT = {}
				return
			end
			local grpdat = plugs.Call( "GetUsergroupData", defgrp )
			if (!grpdat) then
				ErrorNoHalt( "Could not read usergroup data! (" .. defgrp .. ")" )
				self._DAT = {}
				return
			end
			self._DAT = {}
			self._DAT.Privs = grpdat.Privs
			self._DAT.UG = defgrp
			self:SavePlayerData()
		end
		self._DAT = dat
		// self:SetUsergroup( self._DAT.UG )
		timer.Simple( 5, self.SetUsergroup, self, self._DAT.UG )
		if (self:HasPriv( "status_admin" )) then self:SetUserGroup( "admin" ) end
		if (self:HasPriv( "status_superadmin" )) then self:SetUserGroup( "superadmin" ) end
		self:SetNWInt( "gman_timer", self._DAT.Timer or 0 )
	end
	function meta:SavePlayerData()
		plugs.Call( "SavePlayerData", self )
	end
	function meta:BeamGMData( to )
		to = to or self
		local tbl = {}
		tbl[1] = "gmdata"
		tbl[2] = file.Find( "../gamemodes/*" )
		tbl[3] = file.Find( "../maps/*.bsp" )
		datastream.StreamToClients( to, "gmanage", tbl )
	end
end

function PLUG:ConCommand_SV( ply, com, args )
	if (!plugs.Call( "DoConCommand", ply, com, args )) then
		ply:PrintMessage( HUD_PRINTTALK, "You do not have enough privileges to do that!" )
	end
end

function PLUG:DoConCommand( ply, com, args )
	if (com == "set_cvar") then
		return self:DoConCommand( ply, "rcon", args )
	end
	if (com == "getgamemodes") then
		ply.NextGGM = ply.NextGGM or 0
		if (CurTime() >= ply.NextGGM) then
			ply:BeamGMData()
			ply:PrintMessage( HUD_PRINTTALK, "Gamemode and map data refreshed!" )
			ply.NextGGM = CurTime() + 5
		end
		return true
	end
	if (com == "rcon") then
		if (!ply:HasPriv( "rcon" )) then return end
		local rcon = table.concat( args, " " )
		ply:ServerCommand( rcon )
		ply:Log( "rcon", "Ran '" .. rcon .. "'" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " ran '" .. rcon .. "'!", true )
		ply:PrintMessage( HUD_PRINTTALK, "Successfully ran '" .. rcon .. "'!" )
		return true
	end
	if (com == "kick") then
		if (!ply:HasPriv( "kick" )) then return end
		local target = player.GetByGID( tonumber( args[1] or 0 ) )
		if ((!target) || (!target:IsValid())) then return end
		if (!ply:CanDoActionOn( target )) then
			ply:PrintMessage( HUD_PRINTTALK, "You can't do that to that player!" )
			return true
		end
		table.remove( args, 1 )
		local reason = table.concat( args, " " )
		if (reason == "") then reason = "None Specified" end
		ply:Log( "kickban", "Kicked " .. target:SteamID() .. " (" .. (reason or "") .. ")" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " kicked " .. target:Nick() .. "!", true )
		target:Kick( reason )
		return true
	end
	if (com == "ban") then
		if (!ply:HasPriv( "ban" )) then return end
		local target = player.GetByGID( tonumber( args[1] or 0 ) )
		if ((!target) || (!target:IsValid())) then return end
		if (!ply:CanDoActionOn( target )) then
			ply:PrintMessage( HUD_PRINTTALK, "You can't do that to that player!" )
			return true
		end
		local duration = tonumber( args[2] or 0 )
		ply:Log( "kickban", "Banned " .. target:SteamID() .. " (" .. tostring( duration/60 ) .. " mins)" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " banned " .. target:Nick() .. "!", true )
		plugs.Call( "AddToBanList", target:SteamID(), duration, target:Nick() )
		target:Ban( duration )
		return true
	end
	if (com == "slay") then
		if (!ply:HasPriv( "punish" )) then return end
		local target = player.GetByGID( tonumber( args[1] or 0 ) )
		if ((!target) || (!target:IsValid())) then return end
		if (!ply:CanDoActionOn( target )) then
			ply:PrintMessage( HUD_PRINTTALK, "You can't do that to that player!" )
			return true
		end
		ply:Log( "punishment", "Slayed " .. target:SteamID() )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " slayed " .. target:Nick() .. "!", true )
		target:Kill()
		return true
	end
	if (com == "setgrp") then
		if (!ply:HasPriv( "setgroup" )) then return end
		local target = player.GetByGID( tonumber( args[1] or 0 ) )
		if ((!target) || (!target:IsValid())) then return end
		if (!ply:CanDoActionOn( target )) then
			ply:PrintMessage( HUD_PRINTTALK, "You can't do that to that player!" )
			return true
		end
		local ugname = args[2]
		if (!ugname) then return end
		local ugdat = plugs.Call( "GetUsergroupData", ugname )
		if (!ugdat) then return end
		if (ugdat.Rank > ply:GetRank()) then
			ply:PrintMessage( HUD_PRINTTALK, "You can't promote that player above yourself!" )
			return true
		end
		ply:Log( "promote", "Set usergroup of " .. target:SteamID() .. " (" .. (args[2] or "") .. ")" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " set group of " .. target:Nick() .. " to " .. (args[2] or "") .. "!", true )
		ply:PrintMessage( HUD_PRINTTALK, "Changed group of " .. target:Nick() .. "!" )
		target:SetUsergroup( ugname )
		target:BeamPrivs()
		return true
	end
	if (com == "givepriv") then
		if (!ply:HasPriv( "giveprivs" )) then return end
		local target = player.GetByGID( tonumber( args[1] or 0 ) )
		if ((!target) || (!target:IsValid())) then return end
		if (!ply:CanDoActionOn( target )) then
			ply:PrintMessage( HUD_PRINTTALK, "You can't do that to that player!" )
			return true
		end
		if (ply == target) then
			ply:PrintMessage( HUD_PRINTTALK, "You can't do that to yourself!" )
			return true
		end
		local priv = args[2]
		if (!priv) then return end
		if (target:HasPriv( priv )) then
			ply:PrintMessage( HUD_PRINTTALK, "Target already has that privilege!" )
			return true
		end
		ply:Log( "promote", "Gave " .. target:SteamID() .. " '" .. (args[2] or "") .. "'!" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " gave " .. target:Nick() .. " " .. (args[2] or "") .. "!", true )
		target:GivePriv( priv )
		return true
	end
	if (com == "takepriv") then
		if (!ply:HasPriv( "giveprivs" )) then return end
		local target = player.GetByGID( tonumber( args[1] or 0 ) )
		if ((!target) || (!target:IsValid())) then return end
		if (!ply:CanDoActionOn( target )) then
			ply:PrintMessage( HUD_PRINTTALK, "You can't do that to that player!" )
			return true
		end
		if (ply == target) then
			ply:PrintMessage( HUD_PRINTTALK, "You can't do that to yourself!" )
			return true
		end
		local priv = args[2]
		if (!priv) then return end
		if (!target:HasPriv( priv )) then
			ply:PrintMessage( HUD_PRINTTALK, "Target does not have that privilege!" )
			return true
		end
		ply:Log( "promote", "Took from " .. target:SteamID() .. " '" .. (args[2] or "") .. "'!" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " took from " .. target:Nick() .. " '" .. (args[2] or "") .. "'!", true )
		target:TakePriv( priv )
		return true
	end
	if (com == "getprivs") then
		if (!ply:HasPriv( "giveprivs" )) then return end
		local target = player.GetByGID( tonumber( args[1] or 0 ) )
		if ((!target) || (!target:IsValid())) then return end
		target:BeamPrivs( ply )
		return true
	end
	if (com == "givewep") then
		if (!ply:HasPriv( "giveweps" )) then return end
		
		local target = player.GetByGID( tonumber( args[1] or 0 ) )
		if ((!target) || (!target:IsValid())) then return end
		if (!ply:CanDoActionOn( target )) then
			ply:PrintMessage( HUD_PRINTTALK, "You can't do that to that player!" )
			return true
		end
		
		local wep = args[2]
		if (!wep) then return end
		target:Give( wep )
		
		ply:Log( "giveweps", "Gave " .. target:SteamID() .. " '" .. (args[2] or "") .. "'!" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " gave " .. target:Nick() .. " " .. (args[2] or "") .. "!", true )
		
		return true
	end
	if (com == "sv_sethostname") then
		if (!ply:HasPriv( "editserversettings" )) then return end
		local name = table.concat( args, " " )
		if (string.len( name ) < 3) then
			ply:PrintMessage( HUD_PRINTTALK, "Name too short!" )
			return true
		end
		if (string.len( name ) > 30) then
			ply:PrintMessage( HUD_PRINTTALK, "Name too long!" )
			return true
		end
		game.ConsoleCommand( "hostname " .. name .. "\n" )
		ply:PrintMessage( HUD_PRINTTALK, "Hostname changed!" )
		ply:Log( "serversettings", "Changed hostname to '" .. name .. "'" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " changed hostname to " .. name .. "!", true )
		return true
	end
	if (com == "changemap") then
		if (!ply:HasPriv( "changemap" )) then return end
		if (self.MapChanging) then
			ply:PrintMessage( HUD_PRINTTALK, "Map change already in progress!" )
			return true
		end
		local gmname = args[1] or "sandbox"
		local mapname = args[2] or "gm_construct"
		local time = tonumber( args[3] or "0" )
		local f
		for _, v in pairs( file.Find( "../maps/*.bsp" ) ) do
			local map = string.Replace( v, ".bsp", "" )
			if (string.lower( mapname ) == string.lower( map )) then
				f = true
				break
			end
		end
		if (!f) then
			ply:PrintMessage( HUD_PRINTTALK, "Invalid map!" )
			return true
		end
		f = nil
		for _, v in pairs( file.Find( "../gamemodes/*" ) ) do
			if (string.lower( gmname ) == string.lower( v )) then
				f = true
				break
			end
		end
		if (!f) then
			ply:PrintMessage( HUD_PRINTTALK, "Invalid gamemode!" )
			return true
		end
		ply:Log( "mapchange", "Changed to '" .. gmname .. "' on '" .. mapname .. "' (" .. tostring( time ) .. ")" )
		ply:PrintMessage( HUD_PRINTTALK, "Started map change!" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " started map change to " .. mapname .. "!", true )
		if (time == 0) then
			game.ConsoleCommand( "sv_gamemode " .. gmname .. "\n" )
			game.ConsoleCommand( "sv_defaultgamemode " .. gmname .. "\n" )
			game.ConsoleCommand( "changelevel " .. mapname .. "\n" )
			//print( "MAP CHANGE!", gmname, mapname )
		else
			umsg.Start( "gmanage" )
				umsg.String( "mapchange" )
				umsg.String( gmname )
				umsg.String( mapname )
				umsg.Long( time )
			umsg.End()
			timer.Create( "gmanage_mapchange", time, 1, function()
				game.ConsoleCommand( "sv_gamemode " .. gmname .. "\n" )
				game.ConsoleCommand( "sv_defaultgamemode " .. gmname .. "\n" )
				game.ConsoleCommand( "changelevel " .. mapname .. "\n" )
				//print( "MAP CHANGE!", gmname, mapname )
			end )
		end
		self.MapChanging = true
		return true
	end
	if (com == "cancelchangemap") then
		if (!ply:HasPriv( "changemap" )) then return end
		if (!self.MapChanging) then
			ply:PrintMessage( HUD_PRINTTALK, "Map change is not in progress!" )
			return true
		end
		ply:Log( "mapchange", "Cancelled map change!" )
		ply:PrintMessage( HUD_PRINTTALK, "Map change cancelled!" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " cancelled the map change!", true )
		timer.Destroy( "gmanage_mapchange" )
		self.MapChanging = false
		umsg.Start( "gmanage" )
			umsg.String( "cancelmapchange" )
		umsg.End()
		return true
	end
	if (com == "setdefgrp") then
		if (!ply:HasPriv( "editserversettings" )) then return end
		local gname = args[1]
		if (!gname) then return self:Err( ply, "Invalid group!" ) end
		local dat = plugs.Call( "GetUsergroupData", gname )
		if (!dat) then return self:Err( ply, "Group doesn't exist!" ) end
		self:SetServerValue( "DEF_UG", gname )
		ply:Log( "usergroups", "Set default usergroup to " .. gname .. "!" )
		plugs.Call( "SendChatNotice",  ply:Nick() .. " set default usergroup to " .. gname .. "!", true )
		ply:PrintMessage( HUD_PRINTTALK, "Usergroup '" .. gname .. "' set to default!" )
		return true
	end
	if (com == "requestupdate") then
		ply.NGUp = ply.NGUp or 0
		if (CurTime() >= ply.NGUp) then
			ply.NGUp = CurTime()+5
			ply:BeamPrivs()
			ply:BeamUGS()
		end
		return true
	end
end

function PLUG:Err( ply, text )
	ply:PrintMessage( HUD_PRINTTALK, text )
	return true
end

local privs = {
	"all",
	"none",
	"kick",
	"ban",
	"setgroup",
	"punish",
	"teleport",
	"giveweps",
	"giveprivs",
	"manage_players",
	"manage_groups",
	"manage_server",
	"manage_limits",
	"manage_restrict",
	"viewmenu",
	"private_chat",
	"noclip",
	"spray",
	"creategroup",
	"editgroup",
	"deletegroup",
	"changelimits",
	"editserversettings",
	"changemap",
	"physgun_players",
	"physgun_all",
	"toolgun_players",
	"toolgun_all",
	"status_admin",
	"status_superadmin",
	"rcon"
}

function PLUG:IsValidPriv( priv )
	if (table.HasValue( privs, priv )) then return true end
end

local stoolsentsweps
function PLUG:GetStoolSentSweps()
	if (stoolsentsweps) then return stoolsentsweps end
	local tmp = {}
	local toolgun
	for _, v in pairs( weapons.GetList() ) do
		local ent = {}
		ent[1] = "swep"
		ent[2] = v.ClassName
		ent[3] = "[SWEP] " .. string.Replace( (v.PrintName or v.ClassName), "#", "" )
		table.insert( tmp, ent )
		if (v.ClassName == "gmod_tool") then toolgun = v end
	end
	for k, v in pairs( scripted_ents.GetList() ) do
		if (v && v.t && v.t.Spawnable) then
			local ent = {}
			ent[1] = "sent"
			ent[2] = k
			ent[3] = "[SENT] " .. string.Replace( (k or (v.t or {}).PrintName ), "#", "" )
			table.insert( tmp, ent )
		end
	end
	if (toolgun && toolgun.Tool) then
		for k, v in pairs( toolgun.Tool ) do
			local ent = {}
			ent[1] = "stool"
			ent[2] = k
			ent[3] = "[STOOL] " .. string.Replace( (v.Name or k), "#", "" )
			table.insert( tmp, ent )
		end
	end
	stoolsentsweps = tmp
	return tmp
end

/*function PLUG:ReadServerData()
	if (plugs.Call( "Overrided", "serverdata" )) then return end
	if (!file.Exists( DataFile )) then return {} end
	local b, res = pcall( glon.decode, file.Read( DataFile ) )
	if (b) then return res end
	return {}
end*/

function PLUG:LoadServerData()
	self.ServerData = plugs.Call( "ReadServerData" )
end

/*function PLUG:SaveServerData()
	if (plugs.Call( "Overrided", "serverdata" )) then return end
	local b, res = pcall( glon.encode, self.ServerData or {} )
	if (b) then file.Write( DataFile, res ) end
end*/

function PLUG:GetServerData()
	return self.ServerData
end

function PLUG:SetServerValue( id, val )
	if (!self.ServerData) then self:LoadServerData() end
	self.ServerData[ id ] = val
	self:SaveServerData()
end

function PLUG:GetServerValue( id, def )
	if (!self.ServerData) then self:LoadServerData() end
	return self.ServerData[ id ] or def
end

function PLUG:AddToTable( tbl, val )
	if (!table.HasValue( tbl, val )) then table.insert( tbl, val ) end
end

function PLUG:RemoveFromTable( tbl, val )
	if (table.HasValue( tbl, val )) then
		for k, v in pairs( tbl ) do
			if (v == val) then
				table.remove( tbl, k )
				return
			end
		end
	end
end

function PLUG:ApplySwepRestrictions()
	// MESSY: Gmod doesn't actually have any hooks to allow/disallow swep spawning/giving, so
	// we override the console command, and call the base functions ourselves...
	// (Thanks to ASSMod...)
	concommand.Add( "gm_giveswep", function( ply, com, args )
		local swepname = args[1]
		if (!swepname) then return end
		
		// Make sure it's a SWEP
		local swep = weapons.GetStored( swepname ) 
		if (!swep) then return end 
		
		// Can it be spawned at all?
		if ((!swep.Spawnable) && (!swep.AdminSpawnable)) then return end
		
		// Check what the server allows
		local can = plugs.Call( "PlayerSpawnSWEP", ply, swepname )
		if (!can) then return end
		
		// Give it
		ply:Give( swep.Classname ) 
		ply:SelectWeapon( swep.Classname )
		
		// Log it
		ply:Log( "weapons", "Gave themself " .. swepname )
	end )

	concommand.Add( "gm_spawnswep", function( ply, com, args )
		local swepname = args[1]
		if (!swepname) then return end
		
		// Make sure it's a SWEP
		local swep = weapons.GetStored( swepname ) 
		if (!swep) then return end 
		
		// Can it be spawned at all?
		if ((!swep.Spawnable) && (!swep.AdminSpawnable)) then return end
		
		// Check what the server allows
		local can = plugs.Call( "PlayerSpawnSWEP", ply, swepname )
		if (!can) then return end
			
		// Spawn it
		local tr = ply:GetEyeTraceNoCursor() 
		if (!tr.Hit) then return end 

		local ent = ents.Create( swep.Classname ) 
		if (ValidEntity( ent )) then 
			ent:SetPos( tr.HitPos + tr.HitNormal * 32 ) 
			ent:Spawn() 
		end
		
		// Log it
		ply:Log( "weapons", "Spawned " .. swepname )
	end )
end